package com.ssm.redis.util;

import com.alibaba.fastjson.JSON;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 因为之前缓存使用Redis来做，如果当所有的缓存都存储在Redis中的时候，
 * 一旦网络不稳定导致未及时相应，所有请求都可能被阻塞，导致内存和CPU被打满，从而引起重大问题！
 */

//当批量查询商品信息时List<long id>，返回值为Map<id, sku_info>，因为不了解info类型，所以可以使用泛型代替(函数式编程)，或者使用反射(复杂)
@Component
@Slf4j
public class CacheUtil<K, V> {

    //先初始化Cache<String, String> 要操作的缓存数据key和value都是String类型
    private Cache<String, String> localCache = CacheBuilder.newBuilder()
            .maximumSize(5000) //缓存的最大元素数量
            .expireAfterWrite(3, TimeUnit.SECONDS) //过期时间
            .build();

    @Value("${guava.cache.switch}")
    public Boolean switchCache; //后期 配合nacos注册中心动态获取值来判断是否开启本地缓存

    /**
     * 二级缓存先从redis中获取数据
     * @param skuIdList id集合
     * @param cachePrefix 缓存前缀
     * @param clazz 对结果进行序列化，泛型为V
     * @param function Function<入参, 出参>
     * @return
     */
    public Map<K, V> getResult(List<K> skuIdList, String cachePrefix,
                               Class<V> clazz, Function<List<K>, Map<K, V>> function) {
        //参数校验
        if(CollectionUtils.isEmpty(skuIdList)) {
            return Collections.emptyMap();
        }

        Map<K, V> resultMap = new HashMap<>(16);

        if(!switchCache) { //不走本地缓存，直接调用方法
            resultMap = function.apply(skuIdList); //apply调用传入的方法，获取返回值
            return resultMap;
        }

        //id集合可能有些id对应的数据本地缓存没有，把该id存在这个集合中
        List<K> noCacheListId = new ArrayList<>();

        //本地缓存中有的存map，没有的将id存到noCacheListId集合中
        for(K id : skuIdList) {
            String cacheKey = cachePrefix + "_" + id;
            String content = localCache.getIfPresent(cacheKey); //获取本地缓存中key对应的value
            if(StringUtils.isNotBlank(content)) {
                V v = JSON.parseObject(content, clazz); //不为空，把查询结果转为json字符串
                resultMap.put(id, v);
            } else {
                noCacheListId.add(id);
            }
        }

        if(CollectionUtils.isEmpty(noCacheListId)) { //本地缓存中全部存在id集合对应的数据
            return resultMap;
        }

        //调用方法获取map
        Map<K, V> noCacheResultMap = function.apply(noCacheListId);

        if(CollectionUtils.isEmpty(noCacheResultMap)) {
            return resultMap;
        }

        //把调用方法的结果存到map和本地缓存中
        for(Map.Entry<K, V> map : noCacheResultMap.entrySet()) {
            K id = map.getKey();
            V value = map.getValue();
            resultMap.put(id, value);
            String cacheKey = cachePrefix + "_" + id;
            localCache.put(cacheKey, JSON.toJSONString(value));
        }
        return resultMap;
    }
}
